/* audio_codec.c */

/*******************************************************************************
 * Includes
 ******************************************************************************/
#include "audio_codec.h"

#include "fsl_common.h"
#include "fsl_clock.h"
#include "fsl_iocon.h"
#include "fsl_spi.h"
#include "fsl_gpio.h"
#include "fsl_dma.h"
#include "fsl_i2c.h"
#include "fsl_i2s.h"
#include "fsl_i2s_dma.h"
#include "fsl_sysctl.h"

#include "fsl_codec_common.h"
#include "fsl_wm8904.h"

/*******************************************************************************
 * Definitions
 ******************************************************************************/

#define IOCON_PIO_DIGITAL_EN 0x0100u  /*!<@brief Enables digital function */
#define IOCON_PIO_FUNC0 0x00u         /*!<@brief Selects pin function 0 */
#define IOCON_PIO_FUNC1 0x01u         /*!<@brief Selects pin function 1 */
#define IOCON_PIO_FUNC2 0x02u         /*!<@brief Selects pin function 2 */
#define IOCON_PIO_FUNC7 0x07u         /*!<@brief Selects pin function 7 */
#define IOCON_PIO_FUNC5 0x05u         /*!<@brief Selects pin function 5 */
#define IOCON_PIO_FUNC6 0x06u         /*!<@brief Selects pin function 6 */
#define IOCON_PIO_FUNC9 0x09u         /*!<@brief Selects pin function 9 */
#define IOCON_PIO_INV_DI 0x00u        /*!<@brief Input function is not inverted */
#define IOCON_PIO_MODE_INACT 0x00u    /*!<@brief No addition pin function */
#define IOCON_PIO_MODE_PULLUP 0x20u   /*!<@brief Selects pull-up function */
#define IOCON_PIO_OPENDRAIN_DI 0x00u  /*!<@brief Open drain is disabled */
#define IOCON_PIO_SLEW_STANDARD 0x00u /*!<@brief Standard mode, output slew rate control is enabled */
#define IOCON_PIO_ASW_EN 0x00u        /*!<@brief Analog switch input control is enabled */

#define AUDIO_SAMPLE_FREQ_HZ            16000u /* 16khz 采样率. */

#define AUDIO_CODEC_I2C_BASEADDR        (I2C4)
#define AUDIO_CODEC_I2C_CLOCK_FREQ      12000000u
#define AUDIO_CODEC_I2S_MASTER_CLOCK_FREQUENCY (24576000)
#define AUDIO_CODEC_I2S_TX_BASEADDR     (I2S7)
#define AUDIO_CODEC_I2S_RX_BASEADDR     (I2S6)
//#define AUDIO_CODEC_DMA_BASEADDR        (DMA0)
#define AUDIO_CODEC_DMA_I2S_TX_CHANNEL  (19)
#define AUDIO_CODEC_DMA_I2S_RX_CHANNEL  (16)
// #define AUDIO_CODEC_I2S_CLOCK_DIVIDER (CLOCK_GetPll0OutFreq() / 48000 / 16U / 2U)
/* 除以16是为了得到16b数, 除以2是考虑到有两个声道, 永远都有两个声道 */
#define AUDIO_CODEC_I2S_CLOCK_DIVIDER (CLOCK_GetPll0OutFreq() / AUDIO_SAMPLE_FREQ_HZ / 16u / 2u)


/*******************************************************************************
 * Prototypes
 ******************************************************************************/

//static void StartSoundPlayback(void);

/* Codec functions.
 * 识别的过程将放在CodecLoopBack_RxCallback()函数中.
 */
//void CodecLoopBack_Setup(void);
//void CodecLoopBack_Start(void);
static void CodecLoopBack_TxCallback(I2S_Type *base, i2s_dma_handle_t *handle, status_t completionStatus, void *userData);
static void CodecLoopBack_RxCallback(I2S_Type *base, i2s_dma_handle_t *handle, status_t completionStatus, void *userData);

/*******************************************************************************
 * Variables
 ******************************************************************************/

/* 256个点是一个DMA的采样块, 也是FFT的一个处理快.
 * 当前块被填满之后, 前一个块被用于进行数据处理, 再前一个块用于播放.
 */
#define APP_CODEC_AUDIO_BLK_SIZE     256
#define APP_CODEC_AUDIO_BLK_COUNT    3 /* 至少为3 */
volatile uint32_t g_CodecAudioBuff[APP_CODEC_AUDIO_BLK_SIZE*APP_CODEC_AUDIO_BLK_COUNT] = {0}; /* 以4字节分配内存 */
volatile uint32_t g_CodecAudioBlkIdx_Input   = (APP_CODEC_AUDIO_BLK_COUNT-1);
volatile uint32_t g_CodecAudioBlkIdx_Process = (APP_CODEC_AUDIO_BLK_COUNT-2);
volatile uint32_t g_CodecAudioBlkIdx_Output  = (APP_CODEC_AUDIO_BLK_COUNT-3);

/* 一个缓冲区的假数据 */
volatile uint32_t g_CodecAudioBuffDummy[APP_CODEC_AUDIO_BLK_SIZE] = {0}; /* 以4字节分配内存 */

/* dma使用的一系列handler */
static dma_handle_t s_DmaTxHandle;
static dma_handle_t s_DmaRxHandle;
static i2s_dma_handle_t s_TxHandle;
static i2s_dma_handle_t s_RxHandle;
static i2s_transfer_t s_TxTransfer;
static i2s_transfer_t s_RxTransfer;
//static i2s_config_t s_TxConfig;
//static i2s_config_t s_RxConfig;

/* 在捕获到一帧音频数据时触发的回调函数:
 * 音频流中3个数据块中间的一个作为输入数组和输出数组
 * 用户程序可以对传入的输入数据进行处理, 得到计算结果以做他用,
 * 也可以将别的数据写入到音频缓冲区中将被发送出去
 */
audio_data_callback_t audio_data_callback_func;

/*******************************************************************************
 * Code
 ******************************************************************************/
/* 配置Sycctrl仅仅为了处理多个I2S共用引脚的问题. */
static void audio_codec_init_sysctl(void)
{
    SYSCTL_Init(SYSCTL);
    /* select signal source for share set */
    SYSCTL_SetShareSignalSrc(SYSCTL, kSYSCTL_ShareSet0, kSYSCTL_SharedCtrlSignalSCK, kSYSCTL_Flexcomm7);
    SYSCTL_SetShareSignalSrc(SYSCTL, kSYSCTL_ShareSet0, kSYSCTL_SharedCtrlSignalWS, kSYSCTL_Flexcomm7);
    /* select share set for special flexcomm signal */
    SYSCTL_SetShareSet(SYSCTL, kSYSCTL_Flexcomm7, kSYSCTL_FlexcommSignalSCK, kSYSCTL_ShareSet0);
    SYSCTL_SetShareSet(SYSCTL, kSYSCTL_Flexcomm7, kSYSCTL_FlexcommSignalWS, kSYSCTL_ShareSet0);
    SYSCTL_SetShareSet(SYSCTL, kSYSCTL_Flexcomm6, kSYSCTL_FlexcommSignalSCK, kSYSCTL_ShareSet0);
    SYSCTL_SetShareSet(SYSCTL, kSYSCTL_Flexcomm6, kSYSCTL_FlexcommSignalWS, kSYSCTL_ShareSet0);
}

static void audio_codec_i2c_init(void)
{
    //BOARD_I2C_Init(AUDIO_CODEC_I2C_BASEADDR, AUDIO_CODEC_I2C_CLOCK_FREQ);
    i2c_master_config_t i2cConfig = {0};
    I2C_MasterGetDefaultConfig(&i2cConfig);
    I2C_MasterInit(AUDIO_CODEC_I2C_BASEADDR, &i2cConfig, AUDIO_CODEC_I2C_CLOCK_FREQ);
}

static status_t audio_codec_i2c_send(
    uint8_t deviceAddress, uint32_t subAddress, uint8_t subAddressSize,
    const uint8_t *txBuff, uint8_t txBuffSize)
{
    i2c_master_transfer_t masterXfer;

    /* Prepare transfer structure. */
    masterXfer.slaveAddress = deviceAddress;
    masterXfer.direction = kI2C_Write;
    masterXfer.subaddress = subAddress;
    masterXfer.subaddressSize = subAddressSize;
    masterXfer.data = (uint8_t *)txBuff;
    masterXfer.dataSize = txBuffSize;
    masterXfer.flags = kI2C_TransferDefaultFlag;

    return I2C_MasterTransferBlocking(AUDIO_CODEC_I2C_BASEADDR, &masterXfer);
}

static status_t audio_codec_i2c_receive(
    uint8_t deviceAddress, uint32_t subAddress, uint8_t subAddressSize,
    uint8_t *rxBuff, uint8_t rxBuffSize)
{
    i2c_master_transfer_t masterXfer;

    /* Prepare transfer structure. */
    masterXfer.slaveAddress = deviceAddress;
    masterXfer.subaddress = subAddress;
    masterXfer.subaddressSize = subAddressSize;
    masterXfer.data = rxBuff;
    masterXfer.dataSize = rxBuffSize;
    masterXfer.direction = kI2C_Read;
    masterXfer.flags = kI2C_TransferDefaultFlag;

    return I2C_MasterTransferBlocking(AUDIO_CODEC_I2C_BASEADDR, &masterXfer);
}

codec_config_t boardCodecConfig =
{
   /* defined in user's hardware config file. */
   .I2C_SendFunc = audio_codec_i2c_send,
   .I2C_ReceiveFunc = audio_codec_i2c_receive,
   /* defined in fsl_wm8904.c file. */
   .op.Init = WM8904_Init,
   .op.Deinit = WM8904_Deinit,
   .op.SetFormat = WM8904_SetAudioFormat
};

/*
* audio codec总共用了三组外设, 一个i2c用于配置codec工作模式, 两个i2s收发音频数据.
* i2c    -> flexcomm4: sda = PIO1_21, scl = PIO1_20.
* i2s_rx -> flexcomm6: rx  = PIO1_13. 其余信号同flexcomm7复用
* i2s_tx -> flexcomm7: sck = PIO0_21, ws  = PIO0_19, tx = PIO0_20, mclk = PIO1_31.
*/
static void audio_codec_init_pins(void)
{
    CLOCK_EnableClock(kCLOCK_InputMux);
    CLOCK_EnableClock(kCLOCK_Iocon);
    CLOCK_EnableClock(kCLOCK_Gpio0);
    CLOCK_EnableClock(kCLOCK_Gpio1);

    /* for flexcomm4 - i2c4. */
    /* PORT1 PIN20 (coords: 4) is configured as FC4_TXD_SCL_MISO_WS */
    const uint32_t port1_pin20_config = (/* Pin is configured as FC4_TXD_SCL_MISO_WS */
                                         IOCON_PIO_FUNC5 |
                                         /* Selects pull-up function */
                                         IOCON_PIO_MODE_PULLUP |
                                         /* Standard mode, output slew rate control is enabled */
                                         IOCON_PIO_SLEW_STANDARD |
                                         /* Input function is not inverted */
                                         IOCON_PIO_INV_DI |
                                         /* Enables digital function */
                                         IOCON_PIO_DIGITAL_EN |
                                         /* Open drain is disabled */
                                         IOCON_PIO_OPENDRAIN_DI);

    IOCON_PinMuxSet(IOCON, 1U, 20U, port1_pin20_config);

    /* PORT1 PIN21 (coords: 30) is configured as FC4_RXD_SDA_MOSI_DATA */
    const uint32_t port1_pin21_config = (/* Pin is configured as FC4_RXD_SDA_MOSI_DATA */
                                         IOCON_PIO_FUNC5 |
                                         /* Selects pull-up function */
                                         IOCON_PIO_MODE_PULLUP |
                                         /* Standard mode, output slew rate control is enabled */
                                         IOCON_PIO_SLEW_STANDARD |
                                         /* Input function is not inverted */
                                         IOCON_PIO_INV_DI |
                                         /* Enables digital function */
                                         IOCON_PIO_DIGITAL_EN |
                                         /* Open drain is disabled */
                                         IOCON_PIO_OPENDRAIN_DI);
    IOCON_PinMuxSet(IOCON, 1U, 21U, port1_pin21_config);

    /* for flexcomm6 - i2s_rx. */
    /* PORT1 PIN13 (coords: 2) is configured as FC6_RXD_SDA_MOSI_DATA */
    const uint32_t port1_pin13_config = (/* Pin is configured as FC6_RXD_SDA_MOSI_DATA */
                                         IOCON_PIO_FUNC2 |
                                         /* Selects pull-up function */
                                         IOCON_PIO_MODE_PULLUP |
                                         /* Standard mode, output slew rate control is enabled */
                                         IOCON_PIO_SLEW_STANDARD |
                                         /* Input function is not inverted */
                                         IOCON_PIO_INV_DI |
                                         /* Enables digital function */
                                         IOCON_PIO_DIGITAL_EN |
                                         /* Open drain is disabled */
                                         IOCON_PIO_OPENDRAIN_DI);
    IOCON_PinMuxSet(IOCON, 1U, 13U, port1_pin13_config);

    /* for flexcomm7 - i2s_tx. */
    /* PORT0 PIN19 (coords: 90) is configured as FC7_TXD_SCL_MISO_WS */
    const uint32_t port0_pin19_config = (/* Pin is configured as FC7_TXD_SCL_MISO_WS */
                                         IOCON_PIO_FUNC7 |
                                         /* Selects pull-up function */
                                         IOCON_PIO_MODE_PULLUP |
                                         /* Standard mode, output slew rate control is enabled */
                                         IOCON_PIO_SLEW_STANDARD |
                                         /* Input function is not inverted */
                                         IOCON_PIO_INV_DI |
                                         /* Enables digital function */
                                         IOCON_PIO_DIGITAL_EN |
                                         /* Open drain is disabled */
                                         IOCON_PIO_OPENDRAIN_DI);
    IOCON_PinMuxSet(IOCON, 0U, 19U, port0_pin19_config);

    /* PORT0 PIN20 (coords: 74) is configured as FC7_RXD_SDA_MOSI_DATA */
    const uint32_t port0_pin20_config = (/* Pin is configured as FC7_RXD_SDA_MOSI_DATA */
                                         IOCON_PIO_FUNC7 |
                                         /* Selects pull-up function */
                                         IOCON_PIO_MODE_PULLUP |
                                         /* Standard mode, output slew rate control is enabled */
                                         IOCON_PIO_SLEW_STANDARD |
                                         /* Input function is not inverted */
                                         IOCON_PIO_INV_DI |
                                         /* Enables digital function */
                                         IOCON_PIO_DIGITAL_EN |
                                         /* Open drain is disabled */
                                         IOCON_PIO_OPENDRAIN_DI);
    IOCON_PinMuxSet(IOCON, 0U, 20U, port0_pin20_config);

    /* PORT0 PIN21 (coords: 76) is configured as FC7_SCK */
    const uint32_t port0_pin21_config = (/* Pin is configured as FC7_SCK */
                                         IOCON_PIO_FUNC7 |
                                         /* Selects pull-up function */
                                         IOCON_PIO_MODE_PULLUP |
                                         /* Standard mode, output slew rate control is enabled */
                                         IOCON_PIO_SLEW_STANDARD |
                                         /* Input function is not inverted */
                                         IOCON_PIO_INV_DI |
                                         /* Enables digital function */
                                         IOCON_PIO_DIGITAL_EN |
                                         /* Open drain is disabled */
                                         IOCON_PIO_OPENDRAIN_DI);
    IOCON_PinMuxSet(IOCON, 0U, 21U, port0_pin21_config);

    /* PORT1 PIN31 (coords: 91) is configured as MCLK */
    const uint32_t port1_pin31_config = (/* Pin is configured as MCLK */
                                         IOCON_PIO_FUNC1 |
                                         /* No addition pin function */
                                         IOCON_PIO_MODE_INACT |
                                         /* Standard mode, output slew rate control is enabled */
                                         IOCON_PIO_SLEW_STANDARD |
                                         /* Input function is not inverted */
                                         IOCON_PIO_INV_DI |
                                         /* Enables digital function */
                                         IOCON_PIO_DIGITAL_EN |
                                         /* Open drain is disabled */
                                         IOCON_PIO_OPENDRAIN_DI);
    IOCON_PinMuxSet(IOCON, 1U, 31U, port1_pin31_config);
}

static void audio_codec_init_clocks(void)
{
    CLOCK_AttachClk(kFRO12M_to_FLEXCOMM4);/* I2C clock */

    /* 配置PLL时钟源
     * PLL将为I2S的通信引擎提供原始时钟源, I2S的寄存器仍使用系统时钟.
     */
    PMC->PDRUNCFGCLR0 |= PMC_PDSLEEPCFG0_PDEN_XTAL32M_MASK;       /*!< Ensure XTAL16M is on  */
    PMC->PDRUNCFGCLR0 |= PMC_PDSLEEPCFG0_PDEN_LDOXO32M_MASK;      /*!< Ensure XTAL16M is on  */
    //SYSCON->CLOCK_CTRL |= SYSCON_CLOCK_CTRL_CLKIN_ENA_MASK; /*!< Ensure CLK_IN is on  */
    SYSCON->CLOCK_CTRL |= SYSCON_CLOCK_CTRL_CLKIN_FREQM_ENA_MASK; /*!< Ensure CLK_IN is on  */
    ANACTRL->XO32M_CTRL |= ANACTRL_XO32M_CTRL_ENABLE_SYSTEM_CLK_OUT_MASK;

    /*!< Switch PLL0 clock source selector to XTAL16M */
    CLOCK_AttachClk(kEXT_CLK_to_PLL0);

    const pll_setup_t pll0Setup = {
        .pllctrl = SYSCON_PLL0CTRL_CLKEN_MASK | SYSCON_PLL0CTRL_SELI(8U) | SYSCON_PLL0CTRL_SELP(31U),
        .pllndec = SYSCON_PLL0NDEC_NDIV(125U),
        .pllpdec = SYSCON_PLL0PDEC_PDIV(8U),
        .pllsscg = {0x0U, (SYSCON_PLL0SSCG1_MDIV_EXT(3072U) | SYSCON_PLL0SSCG1_SEL_EXT_MASK)},
        .pllRate = 24576000U,
        .flags = PLL_SETUPFLAG_WAITLOCK,
    };
    /*!< Configure PLL to the desired values */
    CLOCK_SetPLL0Freq(&pll0Setup);

    CLOCK_SetClkDiv(kCLOCK_DivPll0Clk, 0U, true);
    CLOCK_SetClkDiv(kCLOCK_DivPll0Clk, 1U, false);

    /* I2S clocks */
    CLOCK_AttachClk(kPLL0_DIV_to_FLEXCOMM6);
    CLOCK_AttachClk(kPLL0_DIV_to_FLEXCOMM7);

    /* Attach PLL clock to MCLK for I2S, no divider */
    CLOCK_AttachClk(kPLL0_to_MCLK);
    SYSCON->MCLKDIV = SYSCON_MCLKDIV_DIV(0U);
    SYSCON->MCLKIO = 1U;
}

void audio_codec_init(void)
{
    i2s_config_t i2s_config;
    wm8904_config_t codecConfig;
    codec_handle_t codecHandle;

    audio_data_callback_func = NULL;

    audio_codec_init_pins();
    audio_codec_init_sysctl(); /* 专为解决多个I2S共用同一个时钟线的冲突. */
    audio_codec_init_clocks();

    RESET_PeripheralReset(kFC4_RST_SHIFT_RSTn);  /* reset FLEXCOMM for I2C */
    RESET_PeripheralReset(kDMA0_RST_SHIFT_RSTn); /* reset FLEXCOMM for DMA0 */
    RESET_PeripheralReset(kFC6_RST_SHIFT_RSTn);  /* reset FLEXCOMM for I2S */
    RESET_PeripheralReset(kFC7_RST_SHIFT_RSTn);

    /* reset NVIC for FLEXCOMM6 and FLEXCOMM7 */
    NVIC_ClearPendingIRQ(FLEXCOMM6_IRQn);
    NVIC_ClearPendingIRQ(FLEXCOMM7_IRQn);

    /* enable interrupts for i2s. */
    NVIC_EnableIRQ(FLEXCOMM6_IRQn);
    NVIC_EnableIRQ(FLEXCOMM7_IRQn);

    /* setup codec. */
    audio_codec_i2c_init();

    WM8904_GetDefaultConfig(&codecConfig);
    codecConfig.format.sampleRate = kWM8904_SampleRate16kHz; /* 16khz */
    codecConfig.mclk_HZ = AUDIO_CODEC_I2S_MASTER_CLOCK_FREQUENCY;
    boardCodecConfig.codecConfig = (void *)&codecConfig;
    if (CODEC_Init(&codecHandle, &boardCodecConfig) != kStatus_Success)
    {
        //PRINTF("WM8904_Init failed!\r\n");
        while (1);
    }
    /* Initial volume kept low for hearing safety. */
    /* Adjust it to your needs, 0x0006 for -51 dB, 0x0039 for 0 dB etc. */
    WM8904_SetVolume(&codecHandle, 0x0030, 0x0030);

    /* setup i2s. */
    I2S_TxGetDefaultConfig(&i2s_config);
    i2s_config.divider = AUDIO_CODEC_I2S_CLOCK_DIVIDER;
    I2S_TxInit(AUDIO_CODEC_I2S_TX_BASEADDR, &i2s_config);
    I2S_RxGetDefaultConfig(&i2s_config);
    I2S_RxInit(AUDIO_CODEC_I2S_RX_BASEADDR, &i2s_config);

    /* setup dma hardware. */
    DMA_Init(DMA0);
    DMA_EnableChannel(DMA0, AUDIO_CODEC_DMA_I2S_TX_CHANNEL);
    DMA_EnableChannel(DMA0, AUDIO_CODEC_DMA_I2S_RX_CHANNEL);
    DMA_SetChannelPriority(DMA0, AUDIO_CODEC_DMA_I2S_TX_CHANNEL, kDMA_ChannelPriority3);
    DMA_SetChannelPriority(DMA0, AUDIO_CODEC_DMA_I2S_RX_CHANNEL, kDMA_ChannelPriority2);
    /* create dma handlers with i2s channel number filled. */
    DMA_CreateHandle(&s_DmaTxHandle, DMA0, AUDIO_CODEC_DMA_I2S_TX_CHANNEL); /* 创建handler并填充通道号 */
    DMA_CreateHandle(&s_DmaRxHandle, DMA0, AUDIO_CODEC_DMA_I2S_RX_CHANNEL);
}

/*
* StartDigitalLoopback
* 注意, 当前版本的i2s dma仅仅支持32数搬运, 也就是说, 即使想要单声道, 也总是会搬运两个声道的数据.
* 实际可以通过配置DMA的搬运带宽为16bit, 仅搬运32位的高16位或低16位, 从而在内存中实现紧凑的16位数排列.
* 这里对缓冲区的使用也是错位操作的, 这样做的目的是减少音频输入DMA, 音频输出DMA和CPU访问内存时产生冲突:
* 走在队列最前面的始终是音频输入DMA, 当音频接收DMA捕获到音频数据后, 直接将刚捕获的数据交给CPU执行处理环节,
* 然后自己将指针移动到下一个缓冲区块中, 在前面的块中填充新捕获到的数据. 音频输出DMA是同输入DMA同步的,
* 但是使用的缓冲区块始终晚于当前的输入DMA两个位置(中间的位置是给处理用的). 这样在任何一个瞬间, 输入和输出DMA的
* 指针同时同步移动, 中间缓冲块的指针由CPU控制, 快速处理.
*/
void audio_codec_start(void)
{
    /* 这里s_RxTransfer和s_TxTransfer内存空间就被注册到handler里去了.
     * 注意, 这里使用的是内存, 而不是其中的内容.
     * 所以,在正式开始传数之前, 这块内存还可以临时存点别的数. */
    I2S_RxTransferCreateHandleDMA(AUDIO_CODEC_I2S_RX_BASEADDR, &s_RxHandle, &s_DmaRxHandle, CodecLoopBack_RxCallback, (void *)&s_RxTransfer);
    I2S_TxTransferCreateHandleDMA(AUDIO_CODEC_I2S_TX_BASEADDR, &s_TxHandle, &s_DmaTxHandle, CodecLoopBack_TxCallback, (void *)&s_TxTransfer);

    /* 先准备一些缓冲区 */

    /* 即将填充的接收buffer. */
    s_RxTransfer.data = (volatile uint8_t *)&g_CodecAudioBuff[g_CodecAudioBlkIdx_Input*APP_CODEC_AUDIO_BLK_SIZE];
    s_RxTransfer.dataSize = APP_CODEC_AUDIO_BLK_SIZE*sizeof(uint32_t); /* 以字节为单位 */
    I2S_RxTransferReceiveDMA(AUDIO_CODEC_I2S_RX_BASEADDR, &s_RxHandle, s_RxTransfer);

    /* 下一个填充的接收buffer. */
    g_CodecAudioBlkIdx_Input = (g_CodecAudioBlkIdx_Input+1)%APP_CODEC_AUDIO_BLK_COUNT; /* 移动接收缓冲区指针 */
    s_RxTransfer.data = (volatile uint8_t *)&g_CodecAudioBuff[g_CodecAudioBlkIdx_Input*APP_CODEC_AUDIO_BLK_SIZE];
    s_RxTransfer.dataSize = APP_CODEC_AUDIO_BLK_SIZE*sizeof(uint32_t);
    I2S_RxTransferReceiveDMA(AUDIO_CODEC_I2S_RX_BASEADDR, &s_RxHandle, s_RxTransfer);

    /* 即将填充的发送buffer 并启动传输. */
    s_TxTransfer.data = (volatile uint8_t *)&g_CodecAudioBuff[g_CodecAudioBlkIdx_Output*APP_CODEC_AUDIO_BLK_SIZE];
    s_TxTransfer.dataSize = APP_CODEC_AUDIO_BLK_SIZE*sizeof(uint32_t);
    I2S_TxTransferSendDMA(AUDIO_CODEC_I2S_TX_BASEADDR, &s_TxHandle, s_TxTransfer);

    /* 紧接着要填充的发送buffer, 并启动传输. */
    g_CodecAudioBlkIdx_Output = (g_CodecAudioBlkIdx_Output+1)%APP_CODEC_AUDIO_BLK_COUNT; /* 移动发送缓冲区指针 */
    s_TxTransfer.data = (volatile uint8_t *)&g_CodecAudioBuff[g_CodecAudioBlkIdx_Output*APP_CODEC_AUDIO_BLK_SIZE];
    s_TxTransfer.dataSize = APP_CODEC_AUDIO_BLK_SIZE*sizeof(uint32_t);
    I2S_TxTransferSendDMA(AUDIO_CODEC_I2S_TX_BASEADDR, &s_TxHandle, s_TxTransfer);
}

/* transfer中的有效信息只是数据指针和数据量. */
static void CodecLoopBack_TxCallback(I2S_Type *base, i2s_dma_handle_t *handle, status_t completionStatus, void *userData)
{
#if 1
    i2s_transfer_t *transfer = (i2s_transfer_t *)userData; /* 这个时候userData指针就是指向s_TxTransfer的. */

    g_CodecAudioBlkIdx_Output = (g_CodecAudioBlkIdx_Output+1)%APP_CODEC_AUDIO_BLK_COUNT; /* 移动发送缓冲区指针 */
    transfer->data = (volatile uint8_t *)&g_CodecAudioBuff[g_CodecAudioBlkIdx_Output*APP_CODEC_AUDIO_BLK_SIZE];
    transfer->dataSize = APP_CODEC_AUDIO_BLK_SIZE*sizeof(uint32_t);
    I2S_TxTransferSendDMA(AUDIO_CODEC_I2S_TX_BASEADDR, handle, *transfer); /* 补入一个新的发送buffer. */
#endif
}

/* 当前版本的i2s dma驱动程序仅能支持32b数据搬运, 未实现16b数据搬运.
 * 此时需要在代码中, 用软件方式将16b有效数据提取出来并紧缩到16b数紧缩的数组中, 从而可以直接传递给PowerQuad进行计算.
 */
//uint16_t app_audio_data_16b[APP_CODEC_AUDIO_BLK_SIZE];
static void CodecLoopBack_RxCallback(I2S_Type *base, i2s_dma_handle_t *handle, status_t completionStatus, void *userData)
{
    i2s_transfer_t *transfer = (i2s_transfer_t *)userData; /* 此时transfer中保存的是刚接收到的数据信息 */

#if 1
     /* 这里引的实用际上就是g_CodecAudioBlkIdx_Process位置的内存 */
    //uint32_t * audio_data_32b_ptr = (uint32_t *)(transfer->data);
    //audio_data_32b_ptr -= APP_CODEC_AUDIO_BLK_COUNT;
    uint32_t audio_data_process_blk_idx = (g_CodecAudioBlkIdx_Input+(APP_CODEC_AUDIO_BLK_COUNT-1))%APP_CODEC_AUDIO_BLK_COUNT; /* 操作下一个发送缓冲区 */
    uint32_t * audio_data_process_32b_ptr = (uint32_t *)(&g_CodecAudioBuff[audio_data_process_blk_idx*APP_CODEC_AUDIO_BLK_SIZE]);

    if (audio_data_callback_func)
    {
        //audio_data_callback_func(audio_data_32b_ptr, audio_data_32b_ptr, APP_CODEC_AUDIO_BLK_SIZE);
        audio_data_callback_func(audio_data_process_32b_ptr, audio_data_process_32b_ptr, APP_CODEC_AUDIO_BLK_SIZE);
    }
#endif

    /* 装入下一个缓冲区 */
    g_CodecAudioBlkIdx_Input = (g_CodecAudioBlkIdx_Input+1)%APP_CODEC_AUDIO_BLK_COUNT; /* 移动接收缓冲区指针 */
    transfer->data = (volatile uint8_t *)&g_CodecAudioBuff[g_CodecAudioBlkIdx_Input*APP_CODEC_AUDIO_BLK_SIZE];
    transfer->dataSize = APP_CODEC_AUDIO_BLK_SIZE*sizeof(uint32_t);
    I2S_RxTransferReceiveDMA(AUDIO_CODEC_I2S_RX_BASEADDR, handle, *transfer); /* 补入一个新的接收buffer. */
}

/* 注册捕获数据后执行数据处理的回调函数. */
void audio_codec_install_callback(audio_data_callback_t func)
{
    audio_data_callback_func = func;
}

/* EOF */

